Introduction

This document will serve as a tutorial for using SCISSORS, with the added functionality of detailing exactly how the PBMC3k dataset figures presented in our manuscript were generated. We’ll start from a 10X counts matrix and end with fully annotated cell clusters. In addition to R, in order to run all the code in this document you’ll need a Python 3 installation with openTSNE and all its dependencies installed.

Libraries

R

library(pals)        # basic colors
library(dplyr)       # tidy data manipulation
library(Seurat)      # single cell infrastructure
library(ggplot2)     # plots
library(SCISSORS)    # our package 
library(paletteer)   # advanced colors
library(reticulate)  # Python interface
library(SeuratData)  # PBMC3k dataset

Python

import numpy as np
from openTSNE import TSNEEmbedding
from openTSNE import initialization
from openTSNE.affinity import Multiscale
from openTSNE.affinity import PerplexityBasedNN

Data

We load a scRNA-seq dataset provided by 10X Genomics that consists of 2,700 peripheral blood mononuclear cells (PBMCs) from a healthy donor.

pbmc <- LoadData("pbmc3k")

Preprocessing

Here we use PrepareData() to normalize expression & select highly variable genes through sctransform, run PCA & t-SNE. and cluster our cells. We utilize 15 principal components even though the Satija Lab vignette used 10, as we use sctransform normalization, which does a better job of retaining biological heterogeneity through normalization than standard log-normalization.

pbmc <- PrepareData(pbmc, 
                    n.variable.genes = 4000, 
                    n.PC = 15, 
                    which.dim.reduc = "tsne",
                    initial.resolution = .4, 
                    random.seed = 629)
## [1] "Normalizing counts using SCTransform"
## [1] "Running t-SNE on 15 principal components with perplexity = 30"
## [1] "Clustering cells in PCA space using k ~ 52 & resolution = 0.4"
## [1] "Found 6 unique clusters"
p0 <- DimPlot(pbmc, cols = tableau20()) + 
      labs(x = "t-SNE 1", y = "t-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p0

Fit-SNE

We’ll also run the Fast Fourier Transform-accelerated version of t-SNE as implemented in the Python library openTSNE. First we’ll need to get our PC matrix into a form accessible by our Python interpreter.

pc_mat <- Embeddings(pbmc, reduction = "pca")
# import PC matrix
pc_mat = np.array(r.pc_mat)
# run Fit-SNE w/ multiscale kernel
init = initialization.pca(pc_mat, random_state=629)
affin_anneal = PerplexityBasedNN(pc_mat, perplexity=100, metric='cosine', random_state=629)
tsne = TSNEEmbedding(init, affin_anneal, negative_gradient_method='fft')
embed1 = tsne.optimize(n_iter=250, exaggeration=10, momentum=0.6)
embed2 = embed1.optimize(n_iter=750, exaggeration=1, momentum=0.8)
affin_anneal.set_perplexity(30)
embed3 = embed2.optimize(n_iter=15, exaggeration=2, momentum=0.8)
embed4 = embed3.optimize(n_iter=250)

Now we pull the results back into R, and save them in our Seurat object. We save the original embedding (made using the default Barnes-Hut t-SNE implementation) in pbmc@reduction$bh_tsne - we need to do this in order to make the Fit-SNE embedding the default embedding that will be retrieved in calls to functions such as DimPlot() or FeaturePlot().

embed <- as.matrix(py$embed4)
rownames(embed) <- colnames(pbmc)
colnames(embed) <- c("Fit-SNE_1", "Fit-SNE_2")
pbmc@reductions$bh_tsne <- pbmc@reductions$tsne
pbmc@reductions$tsne <- CreateDimReducObject(embeddings = embed, 
                                             key = "FitSNE_", 
                                             assay = "SCT", 
                                             global = TRUE)

Visualizing the results shows pretty much the same global structure as with the default t-SNE implementation, albeit rotated a bit, but I like that Fit-SNE’s clusters are a bit denser, so we’ll use Fit-SNE going forward.

p1 <- DimPlot(pbmc, cols = tableau20()) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p1

Reclustering

Here we’ll examine clusters 0, 1, and 4. Cluster 0 seems large enough, and has enough variability on the X-axis of the t-SNE embedding to appear as though it might harbor subgroups. Clusters 1 and 4 have small but visible subclusters.

reclust_res <- ReclusterCells(pbmc, 
                              which.clust = list(0, 1, 4), 
                              n.variable.genes = 4000,
                              n.PC = 15, 
                              k.vals = c(5, 10, 15), 
                              resolution.vals = c(.2, .3, .4), 
                              which.dim.reduc = "tsne", 
                              redo.embedding = TRUE, 
                              random.seed = 629)
DimPlot(reclust_res[[1]], reduction = "tsne")

DimPlot(reclust_res[[2]], reduction = "tsne")

FeaturePlot(reclust_res[[2]], reduction = "tsne", features = "HLA-DPB1")

DimPlot(reclust_res[[3]], reduction = "tsne")

FeaturePlot(reclust_res[[3]], reduction = "tsne", features = "CD14")

## [1] "Reclustering cells in cluster 0 using k = 15 & resolution = 0.3, which achieved silhouette score: 0.27"
## [1] "Reclustering cells in cluster 1 using k = 5 & resolution = 0.3, which achieved silhouette score: 0.27"
## [1] "Reclustering cells in cluster 4 using k = 5 & resolution = 0.3, which achieved silhouette score: 0.524"

Now we’ll run Fit-SNE on each of the reclustered objects for consistencies sake. First we’ll need to isolate the PC matrices and send them to Python.

pc_clust0 <- Embeddings(reclust_res[[1]], reduction = "pca")
pc_clust1 <- Embeddings(reclust_res[[2]], reduction = "pca")
pc_clust2 <- Embeddings(reclust_res[[3]], reduction = "pca")

Running Fit-SNE three times could probably be cleaned up and done in a for loop, but this was easiest to write out in a short time. We use different perplexity sets for each cluster, as they’re all composed of differing numbers of cells.

# import PC matrices
pc_clust0 = np.array(r.pc_clust0)
pc_clust1 = np.array(r.pc_clust1)
pc_clust2 = np.array(r.pc_clust2)

# run Fit-SNE w/ multiscale kernel - cluster 0
affin_multi_clust0 = Multiscale(pc_clust0, perplexities=[15, 50], metric='cosine', random_state=629)
init_clust0 = initialization.pca(pc_clust0, random_state=629)
tsne_obj_clust0 = TSNEEmbedding(init_clust0, affin_multi_clust0, negative_gradient_method='fft')
embed_clust0 = tsne_obj_clust0.optimize(n_iter=300, exaggeration=12, momentum=0.7)
embed_multi_clust0 = embed_clust0.optimize(n_iter=850, exaggeration=1, momentum=0.8)

# run Fit-SNE w/ multiscale kernel - cluster 1
affin_multi_clust1 = Multiscale(pc_clust1, perplexities=[15, 30], metric='cosine', random_state=629)
init_clust1 = initialization.pca(pc_clust1, random_state=629)
tsne_obj_clust1 = TSNEEmbedding(init_clust1, affin_multi_clust1, negative_gradient_method='fft')
embed_clust1 = tsne_obj_clust1.optimize(n_iter=300, exaggeration=12, momentum=0.6)
embed_multi_clust1 = embed_clust1.optimize(n_iter=850, exaggeration=1, momentum=0.8)

# run Fit-SNE w/ multiscale kernel - cluster 4
affin_multi_clust2 = Multiscale(pc_clust2, perplexities=[10, 30], metric='cosine', random_state=629)
init_clust2 = initialization.pca(pc_clust2, random_state=629)
tsne_obj_clust2 = TSNEEmbedding(init_clust2, affin_multi_clust2, negative_gradient_method='fft')
embed_clust2 = tsne_obj_clust2.optimize(n_iter=300, exaggeration=12, momentum=0.6)
embed_multi_clust2 = embed_clust2.optimize(n_iter=750, exaggeration=1, momentum=0.8)

Now we bring the results back in to R.

embed0 <- as.matrix(py$embed_multi_clust0)
rownames(embed0) <- colnames(reclust_res[[1]])
reclust_res[[1]]@reductions$bh_tsne <- reclust_res[[1]]@reductions$tsne
reclust_res[[1]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed0, 
                                                           key = "FitSNE_",
                                                           assay = "SCT", 
                                                           global = TRUE)
embed1 <- as.matrix(py$embed_multi_clust1)
rownames(embed1) <- colnames(reclust_res[[2]])
reclust_res[[2]]@reductions$bh_tsne <- reclust_res[[2]]@reductions$tsne
reclust_res[[2]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed1,
                                                           key = "FitSNE_",
                                                           assay = "SCT",
                                                           global = TRUE)
embed2 <- as.matrix(py$embed_multi_clust2)
rownames(embed2) <- colnames(reclust_res[[3]])
reclust_res[[3]]@reductions$bh_tsne <- reclust_res[[3]]@reductions$tsne
reclust_res[[3]]@reductions$fitsne <- CreateDimReducObject(embeddings = embed2,
                                                           key = "FitSNE_",
                                                           assay = "SCT",
                                                           global = TRUE)

Here’s what our reclusterings look like. There’s clear visual separation between the main clusters and the subgroups we’ve discovered using SCISSORS.

p2 <- DimPlot(reclust_res[[1]], cols = paletteer_d("miscpalettes::brightPastel")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p3 <- DimPlot(reclust_res[[2]], cols = paletteer_d("miscpalettes::brightPastel")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p4 <- DimPlot(reclust_res[[3]], cols = paletteer_d("miscpalettes::brightPastel")) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p2

p3

p4

Next, we’ll reintegrate our new clusters into our original Seurat object - this requires some finagling as Seurat is a bit weird with how it stores cell-level metadata. Since we had six clusters originally, and we discovered six new subclusters, we’ll end up with twelve total clusters.

clust_df <- data.frame(CellID = colnames(pbmc), 
                       ClustID = as.numeric(pbmc@meta.data$seurat_clusters) - 1, 
                       stringsAsFactors = FALSE)
clust_6 <- rownames(reclust_res[[1]]@meta.data[reclust_res[[1]]@meta.data$seurat_clusters == 1, ])
clust_7 <- rownames(reclust_res[[1]]@meta.data[reclust_res[[1]]@meta.data$seurat_clusters == 2, ])
clust_8 <- rownames(reclust_res[[2]]@meta.data[reclust_res[[2]]@meta.data$seurat_clusters == 1, ])
clust_9 <- rownames(reclust_res[[2]]@meta.data[reclust_res[[2]]@meta.data$seurat_clusters == 2, ])
clust_10 <- rownames(reclust_res[[3]]@meta.data[reclust_res[[3]]@meta.data$seurat_clusters == 1, ])
clust_11 <- rownames(reclust_res[[3]]@meta.data[reclust_res[[3]]@meta.data$seurat_clusters == 2, ])
label <- case_when(clust_df$CellID %in% clust_6 ~ 6, 
                   clust_df$CellID %in% clust_7 ~ 7, 
                   clust_df$CellID %in% clust_8 ~ 8, 
                   clust_df$CellID %in% clust_9 ~ 9, 
                   clust_df$CellID %in% clust_10 ~ 10, 
                   clust_df$CellID %in% clust_11 ~ 11, 
                   TRUE ~ clust_df$ClustID)
pbmc <- AddMetaData(pbmc, 
                    col.name = "seurat_clusters", 
                    metadata = as.factor(label))
Idents(pbmc) <- "seurat_clusters"
p5 <- DimPlot(pbmc, cols = tableau20()) + 
      labs(x = "Fit-SNE 1", y = "Fit-SNE 2") + 
      theme_yehlab() + 
      guides(color = guide_legend(nrow = 1, override.aes = list(size = 3)))
p5

Identify Cell Types

Now that we’ve determined our subpopulations, we can assign cell types to each cluster using the marker genes provided in the Satija Lab’s PBMC3k vignette, as well as other canonical markers.

CD4+ T Cells

We can quickly identify cluster 0 as the memory CD4+ T cells, and cluster 6 as the naive CD4+ population.

p6 <- FeaturePlot(pbmc, features = "IL7R") + 
      scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
      theme_yehlab() + 
      NoLegend() + 
      theme(axis.title = element_blank())
p7 <- FeaturePlot(pbmc, features = "CCR7") + 
      scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
      theme_yehlab()  + 
      NoLegend() + 
      theme(axis.title = element_blank())
p8 <- FeaturePlot(pbmc, features = "S100A4") + 
      scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
      theme_yehlab() + 
      NoLegend() + 
      theme(axis.title = element_blank())
(p6 | p7 | p8) / p5

Cluster 7 is only subtly separated from the CD4+ T cell clusters. We’ll use differential expression testing to determine if the cells in cluster 7 are a spurious cluster or a real T cell subtype. After running a Wilcox test we can see that several of the differentially expressed are associated with the interferon family of cytokines and with anti-viral immune responses.

FindAllMarkers(pbmc, 
               assay = "SCT",
               logfc.threshold = .5, 
               only.pos = TRUE, 
               test.use = "wilcox", 
               verbose = FALSE, 
               random.seed = 629) %>% 
  filter(cluster  == 7 & p_val_adj < .05) %>% 
  slice_head(n = 5)
p_val avg_logFC pct.1 pct.2 p_val_adj cluster gene
IFIT1 0 0.6684503 0.575 0.071 0 7 IFIT1
IFIT3 0 0.7114076 0.575 0.098 0 7 IFIT3
IFI61 0 1.3156295 0.950 0.374 0 7 IFI6
ISG151 0 1.3496338 0.950 0.421 0 7 ISG15
MX1 0 0.7809667 0.725 0.195 0 7 MX1

Upon visual inspection of the top three marker genes (as determined by adjusted p-value), we can see that they do an equally good job of distinguishing the small cluster from the sample as a whole as they do at separating it from the memory CD4+ T cells. Due to their anti-viral characteristics , we’ll define this group as being composed of Type 1 helper T cells (Th1).

p9 <- FeaturePlot(pbmc, features = "IFIT1") + 
      scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
      theme_yehlab() + 
      NoLegend() + 
      theme(axis.title = element_blank())
p10 <- FeaturePlot(pbmc, features = "IFIT3") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p11 <- FeaturePlot(pbmc, features = "IFI6") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p9 | p10 | p11) / p5

CD14+ Monocytes

Cluster 1 clearly houses our CD14+ monocytes.

p12 <- FeaturePlot(pbmc, features = "CD14") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p13 <- FeaturePlot(pbmc, features = "LYZ") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p12 | p13) / p5

FCGR3A+ Monocytes

The FCGR3A+ monocytes are positioned near the CD14+ monocytes in cluster 4. Note: these are also known as CD16+ monocytes.

p14 <- FeaturePlot(pbmc, features = "FCGR3A") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p15 <- FeaturePlot(pbmc, features = "MS4A7") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p14 | p15) / p5

Intermediate Monocytes

Lastly, it follows that between the CD14+ and FCGR3A+ monocytes are the intermediate monocytes, which are characterized by expression of CD14 and CD16, along with S100A8, CD74, HLA-DPB1, etc.

p16 <- FeaturePlot(pbmc, features = "HLA-DPB1") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p17 <- FeaturePlot(pbmc, features = "CD74") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p18 <- FeaturePlot(pbmc, features = "HLA-DRA") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p16 | p17 | p18) / p5

B Cells

Expression of MS4A1 allows us to isolate the B cells in cluster 3.

p19 <- FeaturePlot(pbmc, features = "MS4A1") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p19 / p5

CD8+ T Cells

The canonical marker CD8A swiftly identifies our CD8+ T cells in cluster 2.

p20 <- FeaturePlot(pbmc, features = "CD8A") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p20 / p5

Natural Killer Cells

We can use NKG7 and GNLY to isolate the NK cells in cluster 5.

p21 <- FeaturePlot(pbmc, features = "NKG7") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p22 <- FeaturePlot(pbmc, features = "GNLY") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p21 | p22) / p5

Dendritic Cells

The dendritic cell group is defined by expression of FCER1A and CST3 in cluster 9.

p23 <- FeaturePlot(pbmc, features = "FCER1A") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p24 <- FeaturePlot(pbmc, features = "CST3") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
(p23 | p24) / p5

Platelets

The tiny platelet population of 13 cells can be identified by its expression of PPBP in cluster 10.

p25 <- FeaturePlot(pbmc, features = "PPBP") + 
       scale_color_gradientn(colors = wesanderson::wes_palette("Zissou1")) + 
       theme_yehlab() + 
       NoLegend() + 
       theme(axis.title = element_blank())
p25 / p5

Final Figure

Finally, we’ll add cell labels to our original Seurat object and plot the results.

pbmc$label <- case_when(pbmc$seurat_clusters == 0 ~ "Memory CD4+ T", 
                        pbmc$seurat_clusters == 1 ~ "CD14+ Monocyte", 
                        pbmc$seurat_clusters == 2 ~ "CD8+ T", 
                        pbmc$seurat_clusters == 3 ~ "B", 
                        pbmc$seurat_clusters == 4 ~ "FCGR3A+ Monocyte", 
                        pbmc$seurat_clusters == 5 ~ "NK", 
                        pbmc$seurat_clusters == 6 ~ "Naive CD4+ T", 
                        pbmc$seurat_clusters == 7 ~ "Th1", 
                        pbmc$seurat_clusters == 8 ~ "Intermediate Monocyte", 
                        pbmc$seurat_clusters == 9 ~ "DC", 
                        pbmc$seurat_clusters == 10 ~ "Platelet")

We visualize the final cells labels for our 11 clusters.

p26 <- DimPlot(pbmc, cols = paletteer_d("rcartocolor::Vivid"), group.by = "label") + 
       theme_yehlab() + 
       guides(color = guide_legend(nrow = 3, override.aes = list(size = 3))) + 
       labs(x = "Fit-SNE 1", y = "Fit-SNE 2", title = NULL)
p26

Conclusions

SCISSORS automatically split a large CD4+ T cluster into naive, memory, and Th1 CD4+ T cells. It successfully separated a small dendritic cell cluster from the CD14+ monocytes it had originally been grouped with, and teased out the intermediate monocyte population nestled between the CD14+ and CD16+ monocyte clusters. Lastly, it identified a tiny platelet cluster that had been erroneously grouped with the CD16+ monocytes. The intermediate monocyte and Th1 cells were not annotated in the original Satija Lab PBMC3k vignette.

We used the PBMC3k dataset because of 1) its immediate availability to anyone wishing to replicate our results and 2) the validity of its annotations, which allowed us to be confident in the results from SCISSORS, which was able to carve out rare cell groups from larger, broader cell types. In this case, the dendritic cell cluster was composed of 39 cells, the Th1 cluster of 40 cells, and the platelet cluster of just 13 cells. However, SCISSORS isn’t just useful for rare cell types: it also identified the intermediate monocytes, a cluster of 206 cells. We thus believe we can confidently say that SCISSORS has been shown to accurately and swiftly identify cell types, both common and rare, by considering the variance in gene expression within clusters and judging iterative reclustering using silhouette scores. We put forth that this approach is theoretically advantageous for identifying rare cell populations, rather than attempting to do so at the level of the entire dataset.

Save Data & Figures

This part isn’t really worth reading; it’s just here to prove that all the figures were actually dynamically generated and saved upon knitting this document.

We’ll create a quick convenience function to help us save the figures.

saveSCISSORS <- function(plot = NULL, 
                         name = NULL, 
                         border = TRUE, 
                         pub.ready = FALSE) {
  if (is.null(plot) | is.null(name)) stop("You forgot some arguments.")
  if (pub.ready) {
    dir <- "~/Desktop/R/SCISSORS/vignettes/figures_pub/PBMC"
    if (!border) {
      plot <- plot + 
              theme(panel.border = element_blank(), 
                    axis.title = element_blank(), 
                    legend.position = "none")
    } else {
      plot <- plot + 
              theme(axis.title = element_blank(), 
                    legend.position = "none")
    }
    ggsave(filename = paste0(name, ".pdf"), 
           device = "pdf", 
           units = "in",
           path = dir, 
           height = 8, 
           width = 8) 
  } else {
    dir <- "~/Desktop/R/SCISSORS/vignettes/figures_supp/PBMC"
    ggsave(filename = paste0(name, ".pdf"), 
           device = "pdf", 
           units = "in",
           path = dir, 
           height = 8, 
           width = 8) 
  }
}

Lastly, we’ll save the figures under ./vignettes/figures/.

saveSCISSORS(plot = p0, name = "Seurat_Clusters", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p1, name = "Seurat_Clusters_FitSNE", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p2, name = "Clust0_Reclust", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p3, name = "Clust1_Reclust", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p4, name = "Clust2_Reclust", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p5, name = "SCISSORS_Clusters", pub.ready = TRUE, border = FALSE)
saveSCISSORS(plot = p6, name = "CD4T_IL7R")
saveSCISSORS(plot = p7, name = "CD4T_CCR7")
saveSCISSORS(plot = p8, name = "CD4T_S100A4")
saveSCISSORS(plot = p9, name = "TH1_IFIT1")
saveSCISSORS(plot = p10, name = "TH1_IFIT3")
saveSCISSORS(plot = p11, name = "TH1_IFI6")
saveSCISSORS(plot = p12, name = "Monocyte_CD14")
saveSCISSORS(plot = p13, name = "Monocyte_LYZ")
saveSCISSORS(plot = p14, name = "FCGR3A_Monocyte_FCGR3A")
saveSCISSORS(plot = p15, name = "FCGR3A_Monocyte_MS4A7")
saveSCISSORS(plot = p16, name = "Intermediate_Mono_HLADPB1")
saveSCISSORS(plot = p17, name = "Intermediate_Mono_CD74")
saveSCISSORS(plot = p18, name = "Intermediate_Mono_HLADRA")
saveSCISSORS(plot = p19, name = "B_MS4A1")
saveSCISSORS(plot = p20, name = "CD8T_CD8A")
saveSCISSORS(plot = p21, name = "NK_NKG7")
saveSCISSORS(plot = p22, name = "NK_GNLY")
saveSCISSORS(plot = p23, name = "DC_FCER1A")
saveSCISSORS(plot = p24, name = "DC_CST3")
saveSCISSORS(plot = p25, name = "Platelet_PPBP")
saveSCISSORS(plot = p26, name = "SCISSORS_final_labels", pub.ready = TRUE, border = FALSE)

And of course:

sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Catalina 10.15.7
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] parallel  stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] pbmc3k.SeuratData_3.1.4     SeuratData_0.2.1           
##  [3] reticulate_1.18             paletteer_1.3.0            
##  [5] SCISSORS_0.0.2.0            SingleCellExperiment_1.12.0
##  [7] SummarizedExperiment_1.20.0 Biobase_2.50.0             
##  [9] GenomicRanges_1.42.0        GenomeInfoDb_1.26.2        
## [11] IRanges_2.24.1              S4Vectors_0.28.1           
## [13] BiocGenerics_0.36.0         MatrixGenerics_1.2.0       
## [15] matrixStats_0.57.0          data.table_1.13.6          
## [17] cluster_2.1.0               biomaRt_2.44.1             
## [19] ggplot2_3.3.3               Seurat_3.2.3               
## [21] dplyr_1.0.2                 pals_1.6                   
## 
## loaded via a namespace (and not attached):
##   [1] BiocFileCache_1.12.1   plyr_1.8.6             igraph_1.2.6          
##   [4] lazyeval_0.2.2         splines_4.0.3          listenv_0.8.0         
##   [7] scattermore_0.7        digest_0.6.27          htmltools_0.5.0       
##  [10] wesanderson_0.3.6.9000 fansi_0.4.1            magrittr_2.0.1        
##  [13] memoise_1.1.0          tensor_1.5             ROCR_1.0-11           
##  [16] limma_3.44.3           globals_0.14.0         askpass_1.1           
##  [19] prettyunits_1.1.1      colorspace_2.0-0       blob_1.2.1            
##  [22] rappdirs_0.3.1         ggrepel_0.9.0          xfun_0.20             
##  [25] prismatic_1.0.0        crayon_1.3.4           RCurl_1.98-1.2        
##  [28] jsonlite_1.7.2         spatstat_1.64-1        spatstat.data_1.7-0   
##  [31] survival_3.2-7         zoo_1.8-8              glue_1.4.2            
##  [34] polyclip_1.10-0        gtable_0.3.0           zlibbioc_1.36.0       
##  [37] XVector_0.30.0         leiden_0.3.6           DelayedArray_0.16.0   
##  [40] future.apply_1.7.0     maps_3.3.0             abind_1.4-5           
##  [43] scales_1.1.1           DBI_1.1.0              miniUI_0.1.1.1        
##  [46] Rcpp_1.0.5             viridisLite_0.3.0      xtable_1.8-4          
##  [49] progress_1.2.2         bit_4.0.4              rsvd_1.0.3            
##  [52] mapproj_1.2.7          htmlwidgets_1.5.3      httr_1.4.2            
##  [55] RColorBrewer_1.1-2     ellipsis_0.3.1         ica_1.0-2             
##  [58] farver_2.0.3           pkgconfig_2.0.3        XML_3.99-0.5          
##  [61] uwot_0.1.10            dbplyr_2.0.0           deldir_0.2-3          
##  [64] labeling_0.4.2         tidyselect_1.1.0       rlang_0.4.10          
##  [67] reshape2_1.4.4         later_1.1.0.1          AnnotationDbi_1.50.3  
##  [70] munsell_0.5.0          tools_4.0.3            cli_2.2.0             
##  [73] generics_0.1.0         RSQLite_2.2.2          ggridges_0.5.3        
##  [76] evaluate_0.14          stringr_1.4.0          fastmap_1.0.1         
##  [79] yaml_2.2.1             goftest_1.2-2          rematch2_2.1.2        
##  [82] knitr_1.30             bit64_4.0.5            fitdistrplus_1.1-3    
##  [85] purrr_0.3.4            RANN_2.6.1             pbapply_1.4-3         
##  [88] future_1.21.0          nlme_3.1-151           mime_0.9              
##  [91] rstudioapi_0.13        compiler_4.0.3         plotly_4.9.3          
##  [94] curl_4.3               png_0.1-7              spatstat.utils_1.20-2 
##  [97] tibble_3.0.4           stringi_1.5.3          highr_0.8             
## [100] lattice_0.20-41        Matrix_1.3-2           vctrs_0.3.6           
## [103] pillar_1.4.7           lifecycle_0.2.0        lmtest_0.9-38         
## [106] RcppAnnoy_0.0.18       cowplot_1.1.1          bitops_1.0-6          
## [109] irlba_2.3.3            httpuv_1.5.4           patchwork_1.1.1       
## [112] R6_2.5.0               promises_1.1.1         KernSmooth_2.23-18    
## [115] gridExtra_2.3          parallelly_1.23.0      codetools_0.2-18      
## [118] dichromat_2.0-0        MASS_7.3-53            assertthat_0.2.1      
## [121] openssl_1.4.3          withr_2.3.0            sctransform_0.3.2     
## [124] GenomeInfoDbData_1.2.4 mgcv_1.8-33            hms_0.5.3             
## [127] grid_4.0.3             rpart_4.1-15           tidyr_1.1.2           
## [130] rmarkdown_2.6          Rtsne_0.15             shiny_1.5.0
LS0tCnRpdGxlOiAiUEJNQyBBbmFseXNpcyB1c2luZyBTQ0lTU09SUyIKc3VidGl0bGU6ICJKYWNrIExlYXJ5IgphdXRob3I6IAogIC0gIlVuaXZlcnNpdHkgb2YgTm9ydGggQ2Fyb2xpbmEgYXQgQ2hhcGVsIEhpbGwgLSBMaW5lYmVyZ2VyIENvbXByZWhlbnNpdmUgQ2FuY2VyIENlbnRlciIKICAtICJVbml2ZXJzaXR5IG9mIEZsb3JpZGEgLSBEZXBhcnRtZW50IG9mIEJpb3N0YXRpc3RpY3MiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGRmX3ByaW50OiBrYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIikKcmV0aWN1bGF0ZTo6dXNlX3ZpcnR1YWxlbnYoIn4vRGVza3RvcC9QeXRob24vc2NpZW5jZS92ZW52LyIsIHJlcXVpcmVkID0gVFJVRSkKYGBgCgojIEludHJvZHVjdGlvbgoKVGhpcyBkb2N1bWVudCB3aWxsIHNlcnZlIGFzIGEgdHV0b3JpYWwgZm9yIHVzaW5nIGBTQ0lTU09SU2AsIHdpdGggdGhlIGFkZGVkIGZ1bmN0aW9uYWxpdHkgb2YgZGV0YWlsaW5nIGV4YWN0bHkgaG93IHRoZSBQQk1DM2sgZGF0YXNldCBmaWd1cmVzIHByZXNlbnRlZCBpbiBvdXIgbWFudXNjcmlwdCB3ZXJlIGdlbmVyYXRlZC4gV2UnbGwgc3RhcnQgZnJvbSBhIDEwWCBjb3VudHMgbWF0cml4IGFuZCBlbmQgd2l0aCBmdWxseSBhbm5vdGF0ZWQgY2VsbCBjbHVzdGVycy4gSW4gYWRkaXRpb24gdG8gUiwgaW4gb3JkZXIgdG8gcnVuIGFsbCB0aGUgY29kZSBpbiB0aGlzIGRvY3VtZW50IHlvdSdsbCBuZWVkIGEgUHl0aG9uIDMgaW5zdGFsbGF0aW9uIHdpdGggYG9wZW5UU05FYCBhbmQgYWxsIGl0cyBkZXBlbmRlbmNpZXMgaW5zdGFsbGVkLgoKIyBMaWJyYXJpZXMKCiMjIFIKCmBgYHtyfQpsaWJyYXJ5KHBhbHMpICAgICAgICAjIGJhc2ljIGNvbG9ycwpsaWJyYXJ5KGRwbHlyKSAgICAgICAjIHRpZHkgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShTZXVyYXQpICAgICAgIyBzaW5nbGUgY2VsbCBpbmZyYXN0cnVjdHVyZQpsaWJyYXJ5KGdncGxvdDIpICAgICAjIHBsb3RzCmxpYnJhcnkoU0NJU1NPUlMpICAgICMgb3VyIHBhY2thZ2UgCmxpYnJhcnkocGFsZXR0ZWVyKSAgICMgYWR2YW5jZWQgY29sb3JzCmxpYnJhcnkocmV0aWN1bGF0ZSkgICMgUHl0aG9uIGludGVyZmFjZQpsaWJyYXJ5KFNldXJhdERhdGEpICAjIFBCTUMzayBkYXRhc2V0CmBgYAoKIyMgUHl0aG9uCgpgYGB7cHl0aG9ufQppbXBvcnQgbnVtcHkgYXMgbnAKZnJvbSBvcGVuVFNORSBpbXBvcnQgVFNORUVtYmVkZGluZwpmcm9tIG9wZW5UU05FIGltcG9ydCBpbml0aWFsaXphdGlvbgpmcm9tIG9wZW5UU05FLmFmZmluaXR5IGltcG9ydCBNdWx0aXNjYWxlCmZyb20gb3BlblRTTkUuYWZmaW5pdHkgaW1wb3J0IFBlcnBsZXhpdHlCYXNlZE5OCmBgYAoKIyBEYXRhCgpXZSBsb2FkIGEgc2NSTkEtc2VxIGRhdGFzZXQgcHJvdmlkZWQgYnkgMTBYIEdlbm9taWNzIHRoYXQgY29uc2lzdHMgb2YgMiw3MDAgcGVyaXBoZXJhbCBibG9vZCBtb25vbnVjbGVhciBjZWxscyAoUEJNQ3MpIGZyb20gYSBoZWFsdGh5IGRvbm9yLgoKYGBge3J9CnBibWMgPC0gTG9hZERhdGEoInBibWMzayIpCmBgYAoKIyBQcmVwcm9jZXNzaW5nCgpIZXJlIHdlIHVzZSBgUHJlcGFyZURhdGEoKWAgdG8gbm9ybWFsaXplIGV4cHJlc3Npb24gJiBzZWxlY3QgaGlnaGx5IHZhcmlhYmxlIGdlbmVzIHRocm91Z2ggYHNjdHJhbnNmb3JtYCwgcnVuIFBDQSAmIHQtU05FLiBhbmQgY2x1c3RlciBvdXIgY2VsbHMuIFdlIHV0aWxpemUgMTUgcHJpbmNpcGFsIGNvbXBvbmVudHMgZXZlbiB0aG91Z2ggW3RoZSBTYXRpamEgTGFiIHZpZ25ldHRlXSgoaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC92My4yL3BibWMza190dXRvcmlhbC5odG1sKSkgdXNlZCAxMCwgYXMgd2UgdXNlIGBzY3RyYW5zZm9ybWAgbm9ybWFsaXphdGlvbiwgd2hpY2ggZG9lcyBhIGJldHRlciBqb2Igb2YgcmV0YWluaW5nIGJpb2xvZ2ljYWwgaGV0ZXJvZ2VuZWl0eSB0aHJvdWdoIG5vcm1hbGl6YXRpb24gdGhhbiBzdGFuZGFyZCBsb2ctbm9ybWFsaXphdGlvbi4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpwYm1jIDwtIFByZXBhcmVEYXRhKHBibWMsIAogICAgICAgICAgICAgICAgICAgIG4udmFyaWFibGUuZ2VuZXMgPSA0MDAwLCAKICAgICAgICAgICAgICAgICAgICBuLlBDID0gMTUsIAogICAgICAgICAgICAgICAgICAgIHdoaWNoLmRpbS5yZWR1YyA9ICJ0c25lIiwKICAgICAgICAgICAgICAgICAgICBpbml0aWFsLnJlc29sdXRpb24gPSAuNCwgCiAgICAgICAgICAgICAgICAgICAgcmFuZG9tLnNlZWQgPSA2MjkpCnAwIDwtIERpbVBsb3QocGJtYywgY29scyA9IHRhYmxlYXUyMCgpKSArIAogICAgICBsYWJzKHggPSAidC1TTkUgMSIsIHkgPSAidC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwMApgYGAKCiMjIEZpdC1TTkUKCldlJ2xsIGFsc28gcnVuIHRoZSBGYXN0IEZvdXJpZXIgVHJhbnNmb3JtLWFjY2VsZXJhdGVkIHZlcnNpb24gb2YgdC1TTkUgYXMgaW1wbGVtZW50ZWQgaW4gdGhlIFB5dGhvbiBsaWJyYXJ5IGBvcGVuVFNORWAuIEZpcnN0IHdlJ2xsIG5lZWQgdG8gZ2V0IG91ciBQQyBtYXRyaXggaW50byBhIGZvcm0gYWNjZXNzaWJsZSBieSBvdXIgUHl0aG9uIGludGVycHJldGVyLgoKYGBge3J9CnBjX21hdCA8LSBFbWJlZGRpbmdzKHBibWMsIHJlZHVjdGlvbiA9ICJwY2EiKQpgYGAKCmBgYHtweXRob259CiMgaW1wb3J0IFBDIG1hdHJpeApwY19tYXQgPSBucC5hcnJheShyLnBjX21hdCkKIyBydW4gRml0LVNORSB3LyBtdWx0aXNjYWxlIGtlcm5lbAppbml0ID0gaW5pdGlhbGl6YXRpb24ucGNhKHBjX21hdCwgcmFuZG9tX3N0YXRlPTYyOSkKYWZmaW5fYW5uZWFsID0gUGVycGxleGl0eUJhc2VkTk4ocGNfbWF0LCBwZXJwbGV4aXR5PTEwMCwgbWV0cmljPSdjb3NpbmUnLCByYW5kb21fc3RhdGU9NjI5KQp0c25lID0gVFNORUVtYmVkZGluZyhpbml0LCBhZmZpbl9hbm5lYWwsIG5lZ2F0aXZlX2dyYWRpZW50X21ldGhvZD0nZmZ0JykKZW1iZWQxID0gdHNuZS5vcHRpbWl6ZShuX2l0ZXI9MjUwLCBleGFnZ2VyYXRpb249MTAsIG1vbWVudHVtPTAuNikKZW1iZWQyID0gZW1iZWQxLm9wdGltaXplKG5faXRlcj03NTAsIGV4YWdnZXJhdGlvbj0xLCBtb21lbnR1bT0wLjgpCmFmZmluX2FubmVhbC5zZXRfcGVycGxleGl0eSgzMCkKZW1iZWQzID0gZW1iZWQyLm9wdGltaXplKG5faXRlcj0xNSwgZXhhZ2dlcmF0aW9uPTIsIG1vbWVudHVtPTAuOCkKZW1iZWQ0ID0gZW1iZWQzLm9wdGltaXplKG5faXRlcj0yNTApCmBgYAoKTm93IHdlIHB1bGwgdGhlIHJlc3VsdHMgYmFjayBpbnRvIFIsIGFuZCBzYXZlIHRoZW0gaW4gb3VyIGBTZXVyYXRgIG9iamVjdC4gV2Ugc2F2ZSB0aGUgb3JpZ2luYWwgZW1iZWRkaW5nIChtYWRlIHVzaW5nIHRoZSBkZWZhdWx0IEJhcm5lcy1IdXQgdC1TTkUgaW1wbGVtZW50YXRpb24pIGluIGBwYm1jQHJlZHVjdGlvbiRiaF90c25lYCAtIHdlIG5lZWQgdG8gZG8gdGhpcyBpbiBvcmRlciB0byBtYWtlIHRoZSBGaXQtU05FIGVtYmVkZGluZyB0aGUgZGVmYXVsdCBlbWJlZGRpbmcgdGhhdCB3aWxsIGJlIHJldHJpZXZlZCBpbiBjYWxscyB0byBmdW5jdGlvbnMgc3VjaCBhcyBgRGltUGxvdCgpYCBvciBgRmVhdHVyZVBsb3QoKWAuIAoKYGBge3J9CmVtYmVkIDwtIGFzLm1hdHJpeChweSRlbWJlZDQpCnJvd25hbWVzKGVtYmVkKSA8LSBjb2xuYW1lcyhwYm1jKQpjb2xuYW1lcyhlbWJlZCkgPC0gYygiRml0LVNORV8xIiwgIkZpdC1TTkVfMiIpCnBibWNAcmVkdWN0aW9ucyRiaF90c25lIDwtIHBibWNAcmVkdWN0aW9ucyR0c25lCnBibWNAcmVkdWN0aW9ucyR0c25lIDwtIENyZWF0ZURpbVJlZHVjT2JqZWN0KGVtYmVkZGluZ3MgPSBlbWJlZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJGaXRTTkVfIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWwgPSBUUlVFKQpgYGAKClZpc3VhbGl6aW5nIHRoZSByZXN1bHRzIHNob3dzIHByZXR0eSBtdWNoIHRoZSBzYW1lIGdsb2JhbCBzdHJ1Y3R1cmUgYXMgd2l0aCB0aGUgZGVmYXVsdCB0LVNORSBpbXBsZW1lbnRhdGlvbiwgYWxiZWl0IHJvdGF0ZWQgYSBiaXQsIGJ1dCBJIGxpa2UgdGhhdCBGaXQtU05FJ3MgY2x1c3RlcnMgYXJlIGEgYml0IGRlbnNlciwgc28gd2UnbGwgdXNlIEZpdC1TTkUgZ29pbmcgZm9yd2FyZC4KCmBgYHtyfQpwMSA8LSBEaW1QbG90KHBibWMsIGNvbHMgPSB0YWJsZWF1MjAoKSkgKyAKICAgICAgbGFicyh4ID0gIkZpdC1TTkUgMSIsIHkgPSAiRml0LVNORSAyIikgKyAKICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSAxLCBvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzKSkpCnAxCmBgYAoKIyBSZWNsdXN0ZXJpbmcKCkhlcmUgd2UnbGwgZXhhbWluZSBjbHVzdGVycyAwLCAxLCBhbmQgNC4gQ2x1c3RlciAwIHNlZW1zIGxhcmdlIGVub3VnaCwgYW5kIGhhcyBlbm91Z2ggdmFyaWFiaWxpdHkgb24gdGhlIFgtYXhpcyBvZiB0aGUgdC1TTkUgZW1iZWRkaW5nIHRvIGFwcGVhciBhcyB0aG91Z2ggaXQgbWlnaHQgaGFyYm9yIHN1Ymdyb3Vwcy4gQ2x1c3RlcnMgMSBhbmQgNCBoYXZlIHNtYWxsIGJ1dCB2aXNpYmxlIHN1YmNsdXN0ZXJzLgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hvbGQnfQpyZWNsdXN0X3JlcyA8LSBSZWNsdXN0ZXJDZWxscyhwYm1jLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hpY2guY2x1c3QgPSBsaXN0KDAsIDEsIDQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbi52YXJpYWJsZS5nZW5lcyA9IDQwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4uUEMgPSAxNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGsudmFscyA9IGMoNSwgMTAsIDE1KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdXRpb24udmFscyA9IGMoLjIsIC4zLCAuNCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGljaC5kaW0ucmVkdWMgPSAidHNuZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWRvLmVtYmVkZGluZyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb20uc2VlZCA9IDYyOSkKRGltUGxvdChyZWNsdXN0X3Jlc1tbMV1dLCByZWR1Y3Rpb24gPSAidHNuZSIpCkRpbVBsb3QocmVjbHVzdF9yZXNbWzJdXSwgcmVkdWN0aW9uID0gInRzbmUiKQpGZWF0dXJlUGxvdChyZWNsdXN0X3Jlc1tbMl1dLCByZWR1Y3Rpb24gPSAidHNuZSIsIGZlYXR1cmVzID0gIkhMQS1EUEIxIikKRGltUGxvdChyZWNsdXN0X3Jlc1tbM11dLCByZWR1Y3Rpb24gPSAidHNuZSIpCkZlYXR1cmVQbG90KHJlY2x1c3RfcmVzW1szXV0sIHJlZHVjdGlvbiA9ICJ0c25lIiwgZmVhdHVyZXMgPSAiQ0QxNCIpCmBgYAoKTm93IHdlJ2xsIHJ1biBGaXQtU05FIG9uIGVhY2ggb2YgdGhlIHJlY2x1c3RlcmVkIG9iamVjdHMgZm9yIGNvbnNpc3RlbmNpZXMgc2FrZS4gRmlyc3Qgd2UnbGwgbmVlZCB0byBpc29sYXRlIHRoZSBQQyBtYXRyaWNlcyBhbmQgc2VuZCB0aGVtIHRvIFB5dGhvbi4gCgpgYGB7cn0KcGNfY2x1c3QwIDwtIEVtYmVkZGluZ3MocmVjbHVzdF9yZXNbWzFdXSwgcmVkdWN0aW9uID0gInBjYSIpCnBjX2NsdXN0MSA8LSBFbWJlZGRpbmdzKHJlY2x1c3RfcmVzW1syXV0sIHJlZHVjdGlvbiA9ICJwY2EiKQpwY19jbHVzdDIgPC0gRW1iZWRkaW5ncyhyZWNsdXN0X3Jlc1tbM11dLCByZWR1Y3Rpb24gPSAicGNhIikKYGBgCgpSdW5uaW5nIEZpdC1TTkUgdGhyZWUgdGltZXMgY291bGQgcHJvYmFibHkgYmUgY2xlYW5lZCB1cCBhbmQgZG9uZSBpbiBhIGZvciBsb29wLCBidXQgdGhpcyB3YXMgZWFzaWVzdCB0byB3cml0ZSBvdXQgaW4gYSBzaG9ydCB0aW1lLiBXZSB1c2UgZGlmZmVyZW50IHBlcnBsZXhpdHkgc2V0cyBmb3IgZWFjaCBjbHVzdGVyLCBhcyB0aGV5J3JlIGFsbCBjb21wb3NlZCBvZiBkaWZmZXJpbmcgbnVtYmVycyBvZiBjZWxscy4KCmBgYHtweXRob259CiMgaW1wb3J0IFBDIG1hdHJpY2VzCnBjX2NsdXN0MCA9IG5wLmFycmF5KHIucGNfY2x1c3QwKQpwY19jbHVzdDEgPSBucC5hcnJheShyLnBjX2NsdXN0MSkKcGNfY2x1c3QyID0gbnAuYXJyYXkoci5wY19jbHVzdDIpCgojIHJ1biBGaXQtU05FIHcvIG11bHRpc2NhbGUga2VybmVsIC0gY2x1c3RlciAwCmFmZmluX211bHRpX2NsdXN0MCA9IE11bHRpc2NhbGUocGNfY2x1c3QwLCBwZXJwbGV4aXRpZXM9WzE1LCA1MF0sIG1ldHJpYz0nY29zaW5lJywgcmFuZG9tX3N0YXRlPTYyOSkKaW5pdF9jbHVzdDAgPSBpbml0aWFsaXphdGlvbi5wY2EocGNfY2x1c3QwLCByYW5kb21fc3RhdGU9NjI5KQp0c25lX29ial9jbHVzdDAgPSBUU05FRW1iZWRkaW5nKGluaXRfY2x1c3QwLCBhZmZpbl9tdWx0aV9jbHVzdDAsIG5lZ2F0aXZlX2dyYWRpZW50X21ldGhvZD0nZmZ0JykKZW1iZWRfY2x1c3QwID0gdHNuZV9vYmpfY2x1c3QwLm9wdGltaXplKG5faXRlcj0zMDAsIGV4YWdnZXJhdGlvbj0xMiwgbW9tZW50dW09MC43KQplbWJlZF9tdWx0aV9jbHVzdDAgPSBlbWJlZF9jbHVzdDAub3B0aW1pemUobl9pdGVyPTg1MCwgZXhhZ2dlcmF0aW9uPTEsIG1vbWVudHVtPTAuOCkKCiMgcnVuIEZpdC1TTkUgdy8gbXVsdGlzY2FsZSBrZXJuZWwgLSBjbHVzdGVyIDEKYWZmaW5fbXVsdGlfY2x1c3QxID0gTXVsdGlzY2FsZShwY19jbHVzdDEsIHBlcnBsZXhpdGllcz1bMTUsIDMwXSwgbWV0cmljPSdjb3NpbmUnLCByYW5kb21fc3RhdGU9NjI5KQppbml0X2NsdXN0MSA9IGluaXRpYWxpemF0aW9uLnBjYShwY19jbHVzdDEsIHJhbmRvbV9zdGF0ZT02MjkpCnRzbmVfb2JqX2NsdXN0MSA9IFRTTkVFbWJlZGRpbmcoaW5pdF9jbHVzdDEsIGFmZmluX211bHRpX2NsdXN0MSwgbmVnYXRpdmVfZ3JhZGllbnRfbWV0aG9kPSdmZnQnKQplbWJlZF9jbHVzdDEgPSB0c25lX29ial9jbHVzdDEub3B0aW1pemUobl9pdGVyPTMwMCwgZXhhZ2dlcmF0aW9uPTEyLCBtb21lbnR1bT0wLjYpCmVtYmVkX211bHRpX2NsdXN0MSA9IGVtYmVkX2NsdXN0MS5vcHRpbWl6ZShuX2l0ZXI9ODUwLCBleGFnZ2VyYXRpb249MSwgbW9tZW50dW09MC44KQoKIyBydW4gRml0LVNORSB3LyBtdWx0aXNjYWxlIGtlcm5lbCAtIGNsdXN0ZXIgNAphZmZpbl9tdWx0aV9jbHVzdDIgPSBNdWx0aXNjYWxlKHBjX2NsdXN0MiwgcGVycGxleGl0aWVzPVsxMCwgMzBdLCBtZXRyaWM9J2Nvc2luZScsIHJhbmRvbV9zdGF0ZT02MjkpCmluaXRfY2x1c3QyID0gaW5pdGlhbGl6YXRpb24ucGNhKHBjX2NsdXN0MiwgcmFuZG9tX3N0YXRlPTYyOSkKdHNuZV9vYmpfY2x1c3QyID0gVFNORUVtYmVkZGluZyhpbml0X2NsdXN0MiwgYWZmaW5fbXVsdGlfY2x1c3QyLCBuZWdhdGl2ZV9ncmFkaWVudF9tZXRob2Q9J2ZmdCcpCmVtYmVkX2NsdXN0MiA9IHRzbmVfb2JqX2NsdXN0Mi5vcHRpbWl6ZShuX2l0ZXI9MzAwLCBleGFnZ2VyYXRpb249MTIsIG1vbWVudHVtPTAuNikKZW1iZWRfbXVsdGlfY2x1c3QyID0gZW1iZWRfY2x1c3QyLm9wdGltaXplKG5faXRlcj03NTAsIGV4YWdnZXJhdGlvbj0xLCBtb21lbnR1bT0wLjgpCmBgYAoKTm93IHdlIGJyaW5nIHRoZSByZXN1bHRzIGJhY2sgaW4gdG8gUi4KCmBgYHtyfQplbWJlZDAgPC0gYXMubWF0cml4KHB5JGVtYmVkX211bHRpX2NsdXN0MCkKcm93bmFtZXMoZW1iZWQwKSA8LSBjb2xuYW1lcyhyZWNsdXN0X3Jlc1tbMV1dKQpyZWNsdXN0X3Jlc1tbMV1dQHJlZHVjdGlvbnMkYmhfdHNuZSA8LSByZWNsdXN0X3Jlc1tbMV1dQHJlZHVjdGlvbnMkdHNuZQpyZWNsdXN0X3Jlc1tbMV1dQHJlZHVjdGlvbnMkZml0c25lIDwtIENyZWF0ZURpbVJlZHVjT2JqZWN0KGVtYmVkZGluZ3MgPSBlbWJlZDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJGaXRTTkVfIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJTQ1QiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWwgPSBUUlVFKQplbWJlZDEgPC0gYXMubWF0cml4KHB5JGVtYmVkX211bHRpX2NsdXN0MSkKcm93bmFtZXMoZW1iZWQxKSA8LSBjb2xuYW1lcyhyZWNsdXN0X3Jlc1tbMl1dKQpyZWNsdXN0X3Jlc1tbMl1dQHJlZHVjdGlvbnMkYmhfdHNuZSA8LSByZWNsdXN0X3Jlc1tbMl1dQHJlZHVjdGlvbnMkdHNuZQpyZWNsdXN0X3Jlc1tbMl1dQHJlZHVjdGlvbnMkZml0c25lIDwtIENyZWF0ZURpbVJlZHVjT2JqZWN0KGVtYmVkZGluZ3MgPSBlbWJlZDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gIkZpdFNORV8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlNDVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2xvYmFsID0gVFJVRSkKZW1iZWQyIDwtIGFzLm1hdHJpeChweSRlbWJlZF9tdWx0aV9jbHVzdDIpCnJvd25hbWVzKGVtYmVkMikgPC0gY29sbmFtZXMocmVjbHVzdF9yZXNbWzNdXSkKcmVjbHVzdF9yZXNbWzNdXUByZWR1Y3Rpb25zJGJoX3RzbmUgPC0gcmVjbHVzdF9yZXNbWzNdXUByZWR1Y3Rpb25zJHRzbmUKcmVjbHVzdF9yZXNbWzNdXUByZWR1Y3Rpb25zJGZpdHNuZSA8LSBDcmVhdGVEaW1SZWR1Y09iamVjdChlbWJlZGRpbmdzID0gZW1iZWQyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJGaXRTTkVfIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJTQ1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsb2JhbCA9IFRSVUUpCmBgYAoKSGVyZSdzIHdoYXQgb3VyIHJlY2x1c3RlcmluZ3MgbG9vayBsaWtlLiBUaGVyZSdzIGNsZWFyIHZpc3VhbCBzZXBhcmF0aW9uIGJldHdlZW4gdGhlIG1haW4gY2x1c3RlcnMgYW5kIHRoZSBzdWJncm91cHMgd2UndmUgZGlzY292ZXJlZCB1c2luZyBgU0NJU1NPUlNgLgoKYGBge3J9CnAyIDwtIERpbVBsb3QocmVjbHVzdF9yZXNbWzFdXSwgY29scyA9IHBhbGV0dGVlcl9kKCJtaXNjcGFsZXR0ZXM6OmJyaWdodFBhc3RlbCIpKSArIAogICAgICBsYWJzKHggPSAiRml0LVNORSAxIiwgeSA9ICJGaXQtU05FIDIiKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDEsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMpKSkKcDMgPC0gRGltUGxvdChyZWNsdXN0X3Jlc1tbMl1dLCBjb2xzID0gcGFsZXR0ZWVyX2QoIm1pc2NwYWxldHRlczo6YnJpZ2h0UGFzdGVsIikpICsgCiAgICAgIGxhYnMoeCA9ICJGaXQtU05FIDEiLCB5ID0gIkZpdC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwNCA8LSBEaW1QbG90KHJlY2x1c3RfcmVzW1szXV0sIGNvbHMgPSBwYWxldHRlZXJfZCgibWlzY3BhbGV0dGVzOjpicmlnaHRQYXN0ZWwiKSkgKyAKICAgICAgbGFicyh4ID0gIkZpdC1TTkUgMSIsIHkgPSAiRml0LVNORSAyIikgKyAKICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSAxLCBvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzKSkpCnAyCnAzCnA0CmBgYAoKTmV4dCwgd2UnbGwgcmVpbnRlZ3JhdGUgb3VyIG5ldyBjbHVzdGVycyBpbnRvIG91ciBvcmlnaW5hbCBgU2V1cmF0YCBvYmplY3QgLSB0aGlzIHJlcXVpcmVzIHNvbWUgZmluYWdsaW5nIGFzIGBTZXVyYXRgIGlzIGEgYml0IHdlaXJkIHdpdGggaG93IGl0IHN0b3JlcyBjZWxsLWxldmVsIG1ldGFkYXRhLiBTaW5jZSB3ZSBoYWQgc2l4IGNsdXN0ZXJzIG9yaWdpbmFsbHksIGFuZCB3ZSBkaXNjb3ZlcmVkIHNpeCBuZXcgc3ViY2x1c3RlcnMsIHdlJ2xsIGVuZCB1cCB3aXRoIHR3ZWx2ZSB0b3RhbCBjbHVzdGVycy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpjbHVzdF9kZiA8LSBkYXRhLmZyYW1lKENlbGxJRCA9IGNvbG5hbWVzKHBibWMpLCAKICAgICAgICAgICAgICAgICAgICAgICBDbHVzdElEID0gYXMubnVtZXJpYyhwYm1jQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMpIC0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpjbHVzdF82IDwtIHJvd25hbWVzKHJlY2x1c3RfcmVzW1sxXV1AbWV0YS5kYXRhW3JlY2x1c3RfcmVzW1sxXV1AbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAxLCBdKQpjbHVzdF83IDwtIHJvd25hbWVzKHJlY2x1c3RfcmVzW1sxXV1AbWV0YS5kYXRhW3JlY2x1c3RfcmVzW1sxXV1AbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAyLCBdKQpjbHVzdF84IDwtIHJvd25hbWVzKHJlY2x1c3RfcmVzW1syXV1AbWV0YS5kYXRhW3JlY2x1c3RfcmVzW1syXV1AbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAxLCBdKQpjbHVzdF85IDwtIHJvd25hbWVzKHJlY2x1c3RfcmVzW1syXV1AbWV0YS5kYXRhW3JlY2x1c3RfcmVzW1syXV1AbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyA9PSAyLCBdKQpjbHVzdF8xMCA8LSByb3duYW1lcyhyZWNsdXN0X3Jlc1tbM11dQG1ldGEuZGF0YVtyZWNsdXN0X3Jlc1tbM11dQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0gMSwgXSkKY2x1c3RfMTEgPC0gcm93bmFtZXMocmVjbHVzdF9yZXNbWzNdXUBtZXRhLmRhdGFbcmVjbHVzdF9yZXNbWzNdXUBtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzID09IDIsIF0pCmxhYmVsIDwtIGNhc2Vfd2hlbihjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF82IH4gNiwgCiAgICAgICAgICAgICAgICAgICBjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF83IH4gNywgCiAgICAgICAgICAgICAgICAgICBjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF84IH4gOCwgCiAgICAgICAgICAgICAgICAgICBjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF85IH4gOSwgCiAgICAgICAgICAgICAgICAgICBjbHVzdF9kZiRDZWxsSUQgJWluJSBjbHVzdF8xMCB+IDEwLCAKICAgICAgICAgICAgICAgICAgIGNsdXN0X2RmJENlbGxJRCAlaW4lIGNsdXN0XzExIH4gMTEsIAogICAgICAgICAgICAgICAgICAgVFJVRSB+IGNsdXN0X2RmJENsdXN0SUQpCnBibWMgPC0gQWRkTWV0YURhdGEocGJtYywgCiAgICAgICAgICAgICAgICAgICAgY29sLm5hbWUgPSAic2V1cmF0X2NsdXN0ZXJzIiwgCiAgICAgICAgICAgICAgICAgICAgbWV0YWRhdGEgPSBhcy5mYWN0b3IobGFiZWwpKQpJZGVudHMocGJtYykgPC0gInNldXJhdF9jbHVzdGVycyIKcDUgPC0gRGltUGxvdChwYm1jLCBjb2xzID0gdGFibGVhdTIwKCkpICsgCiAgICAgIGxhYnMoeCA9ICJGaXQtU05FIDEiLCB5ID0gIkZpdC1TTkUgMiIpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKQpwNQpgYGAKCiMgSWRlbnRpZnkgQ2VsbCBUeXBlcwoKTm93IHRoYXQgd2UndmUgZGV0ZXJtaW5lZCBvdXIgc3VicG9wdWxhdGlvbnMsIHdlIGNhbiBhc3NpZ24gY2VsbCB0eXBlcyB0byBlYWNoIGNsdXN0ZXIgdXNpbmcgdGhlIG1hcmtlciBnZW5lcyBwcm92aWRlZCBpbiBbdGhlIFNhdGlqYSBMYWIncyBQQk1DM2sgdmlnbmV0dGVdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvdjMuMi9wYm1jM2tfdHV0b3JpYWwuaHRtbCksIGFzIHdlbGwgYXMgb3RoZXIgY2Fub25pY2FsIG1hcmtlcnMuCgojIyBDRDQrIFQgQ2VsbHMKCldlIGNhbiBxdWlja2x5IGlkZW50aWZ5IGNsdXN0ZXIgMCBhcyB0aGUgbWVtb3J5IENENCsgVCBjZWxscywgYW5kIGNsdXN0ZXIgNiBhcyB0aGUgbmFpdmUgQ0Q0KyBwb3B1bGF0aW9uLgoKYGBge3J9CnA2IDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIklMN1IiKSArIAogICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDcgPC0gRmVhdHVyZVBsb3QocGJtYywgZmVhdHVyZXMgPSAiQ0NSNyIpICsgCiAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgdGhlbWVfeWVobGFiKCkgICsgCiAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDggPC0gRmVhdHVyZVBsb3QocGJtYywgZmVhdHVyZXMgPSAiUzEwMEE0IikgKyAKICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICBOb0xlZ2VuZCgpICsgCiAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCihwNiB8IHA3IHwgcDgpIC8gcDUKYGBgCgpDbHVzdGVyIDcgaXMgb25seSBzdWJ0bHkgc2VwYXJhdGVkIGZyb20gdGhlIENENCsgVCBjZWxsIGNsdXN0ZXJzLiBXZSdsbCB1c2UgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gdGVzdGluZyB0byBkZXRlcm1pbmUgaWYgdGhlIGNlbGxzIGluIGNsdXN0ZXIgNyBhcmUgYSBzcHVyaW91cyBjbHVzdGVyIG9yIGEgcmVhbCBUIGNlbGwgc3VidHlwZS4gQWZ0ZXIgcnVubmluZyBhIFdpbGNveCB0ZXN0IHdlIGNhbiBzZWUgdGhhdCBzZXZlcmFsIG9mIHRoZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgYXJlIGFzc29jaWF0ZWQgd2l0aCB0aGUgaW50ZXJmZXJvbiBmYW1pbHkgb2YgY3l0b2tpbmVzIGFuZCB3aXRoIGFudGktdmlyYWwgaW1tdW5lIHJlc3BvbnNlcy4KCmBgYHtyfQpGaW5kQWxsTWFya2VycyhwYm1jLCAKICAgICAgICAgICAgICAgYXNzYXkgPSAiU0NUIiwKICAgICAgICAgICAgICAgbG9nZmMudGhyZXNob2xkID0gLjUsIAogICAgICAgICAgICAgICBvbmx5LnBvcyA9IFRSVUUsIAogICAgICAgICAgICAgICB0ZXN0LnVzZSA9ICJ3aWxjb3giLCAKICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgcmFuZG9tLnNlZWQgPSA2MjkpICU+JSAKICBmaWx0ZXIoY2x1c3RlciAgPT0gNyAmIHBfdmFsX2FkaiA8IC4wNSkgJT4lIAogIHNsaWNlX2hlYWQobiA9IDUpCmBgYAoKVXBvbiB2aXN1YWwgaW5zcGVjdGlvbiBvZiB0aGUgdG9wIHRocmVlIG1hcmtlciBnZW5lcyAoYXMgZGV0ZXJtaW5lZCBieSBhZGp1c3RlZCBwLXZhbHVlKSwgd2UgY2FuIHNlZSB0aGF0IHRoZXkgZG8gYW4gZXF1YWxseSBnb29kIGpvYiBvZiBkaXN0aW5ndWlzaGluZyB0aGUgc21hbGwgY2x1c3RlciBmcm9tIHRoZSBzYW1wbGUgYXMgYSB3aG9sZSBhcyB0aGV5IGRvIGF0IHNlcGFyYXRpbmcgaXQgZnJvbSB0aGUgbWVtb3J5IENENCsgVCBjZWxscy4gRHVlIHRvIHRoZWlyIGFudGktdmlyYWwgY2hhcmFjdGVyaXN0aWNzICwgd2UnbGwgZGVmaW5lIHRoaXMgZ3JvdXAgYXMgYmVpbmcgY29tcG9zZWQgb2YgVHlwZSAxIGhlbHBlciBUIGNlbGxzIChUaDEpLgoKYGBge3J9CnA5IDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIklGSVQxIikgKyAKICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICBOb0xlZ2VuZCgpICsgCiAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxMCA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJJRklUMyIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDExIDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIklGSTYiKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCihwOSB8IHAxMCB8IHAxMSkgLyBwNQpgYGAKCiMjIENEMTQrIE1vbm9jeXRlcwoKQ2x1c3RlciAxIGNsZWFybHkgaG91c2VzIG91ciBDRDE0KyBtb25vY3l0ZXMuCgpgYGB7cn0KcDEyIDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIkNEMTQiKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxMyA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJMWVoiKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCihwMTIgfCBwMTMpIC8gcDUKYGBgCgojIyBGQ0dSM0ErIE1vbm9jeXRlcwoKVGhlIEZDR1IzQSsgbW9ub2N5dGVzIGFyZSBwb3NpdGlvbmVkIG5lYXIgdGhlIENEMTQrIG1vbm9jeXRlcyBpbiBjbHVzdGVyIDQuIE5vdGU6IHRoZXNlIGFyZSBhbHNvIGtub3duIGFzIENEMTYrIG1vbm9jeXRlcy4gCgpgYGB7cn0KcDE0IDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIkZDR1IzQSIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDE1IDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIk1TNEE3IikgKyAKICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQoocDE0IHwgcDE1KSAvIHA1CmBgYAoKIyMgSW50ZXJtZWRpYXRlIE1vbm9jeXRlcwoKTGFzdGx5LCBpdCBmb2xsb3dzIHRoYXQgYmV0d2VlbiB0aGUgQ0QxNCsgYW5kIEZDR1IzQSsgbW9ub2N5dGVzIGFyZSB0aGUgaW50ZXJtZWRpYXRlIG1vbm9jeXRlcywgd2hpY2ggW2FyZSBjaGFyYWN0ZXJpemVkIGJ5XShodHRwczovL3d3dy5mcm9udGllcnNpbi5vcmcvYXJ0aWNsZXMvMTAuMzM4OS9maW1tdS4yMDE5LjAyMDM1L2Z1bGwjQjMpIGV4cHJlc3Npb24gb2YgQ0QxNCBhbmQgQ0QxNiwgYWxvbmcgd2l0aCBTMTAwQTgsIENENzQsIEhMQS1EUEIxLCBldGMuIAoKYGBge3J9CnAxNiA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJITEEtRFBCMSIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDE3IDwtIEZlYXR1cmVQbG90KHBibWMsIGZlYXR1cmVzID0gIkNENzQiKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAxOCA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJITEEtRFJBIikgKyAKICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQoocDE2IHwgcDE3IHwgcDE4KSAvIHA1CmBgYAoKCiMjIEIgQ2VsbHMKCkV4cHJlc3Npb24gb2YgTVM0QTEgYWxsb3dzIHVzIHRvIGlzb2xhdGUgdGhlIEIgY2VsbHMgaW4gY2x1c3RlciAzLgoKYGBge3J9CnAxOSA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJNUzRBMSIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDE5IC8gcDUKYGBgCgojIyBDRDgrIFQgQ2VsbHMKClRoZSBjYW5vbmljYWwgbWFya2VyIENEOEEgc3dpZnRseSBpZGVudGlmaWVzIG91ciBDRDgrIFQgY2VsbHMgaW4gY2x1c3RlciAyLgoKYGBge3J9CnAyMCA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJDRDhBIikgKyAKICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMjAgLyBwNQpgYGAKCiMjIE5hdHVyYWwgS2lsbGVyIENlbGxzCgpXZSBjYW4gdXNlIE5LRzcgYW5kIEdOTFkgdG8gaXNvbGF0ZSB0aGUgTksgY2VsbHMgaW4gY2x1c3RlciA1LgoKYGBge3J9CnAyMSA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJOS0c3IikgKyAKICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpwMjIgPC0gRmVhdHVyZVBsb3QocGJtYywgZmVhdHVyZXMgPSAiR05MWSIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKKHAyMSB8IHAyMikgLyBwNQpgYGAKCiMjIERlbmRyaXRpYyBDZWxscwoKVGhlIGRlbmRyaXRpYyBjZWxsIGdyb3VwIGlzIGRlZmluZWQgYnkgZXhwcmVzc2lvbiBvZiBGQ0VSMUEgYW5kIENTVDMgaW4gY2x1c3RlciA5LgoKYGBge3J9CnAyMyA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJGQ0VSMUEiKSArIAogICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IHdlc2FuZGVyc29uOjp3ZXNfcGFsZXR0ZSgiWmlzc291MSIpKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIE5vTGVnZW5kKCkgKyAKICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCnAyNCA8LSBGZWF0dXJlUGxvdChwYm1jLCBmZWF0dXJlcyA9ICJDU1QzIikgKyAKICAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSB3ZXNhbmRlcnNvbjo6d2VzX3BhbGV0dGUoIlppc3NvdTEiKSkgKyAKICAgICAgIHRoZW1lX3llaGxhYigpICsgCiAgICAgICBOb0xlZ2VuZCgpICsgCiAgICAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQoocDIzIHwgcDI0KSAvIHA1CmBgYAoKIyMgUGxhdGVsZXRzCgpUaGUgdGlueSBwbGF0ZWxldCBwb3B1bGF0aW9uIG9mIGByIHN1bShwYm1jJHNldXJhdF9jbHVzdGVycyA9PSAxMClgIGNlbGxzIGNhbiBiZSBpZGVudGlmaWVkIGJ5IGl0cyBleHByZXNzaW9uIG9mIFBQQlAgaW4gY2x1c3RlciAxMC4KCmBgYHtyfQpwMjUgPC0gRmVhdHVyZVBsb3QocGJtYywgZmVhdHVyZXMgPSAiUFBCUCIpICsgCiAgICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gd2VzYW5kZXJzb246Ondlc19wYWxldHRlKCJaaXNzb3UxIikpICsgCiAgICAgICB0aGVtZV95ZWhsYWIoKSArIAogICAgICAgTm9MZWdlbmQoKSArIAogICAgICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKcDI1IC8gcDUKYGBgCgojIyBGaW5hbCBGaWd1cmUKCkZpbmFsbHksIHdlJ2xsIGFkZCBjZWxsIGxhYmVscyB0byBvdXIgb3JpZ2luYWwgYFNldXJhdGAgb2JqZWN0IGFuZCBwbG90IHRoZSByZXN1bHRzLgoKYGBge3J9CnBibWMkbGFiZWwgPC0gY2FzZV93aGVuKHBibWMkc2V1cmF0X2NsdXN0ZXJzID09IDAgfiAiTWVtb3J5IENENCsgVCIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSAxIH4gIkNEMTQrIE1vbm9jeXRlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBibWMkc2V1cmF0X2NsdXN0ZXJzID09IDIgfiAiQ0Q4KyBUIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBibWMkc2V1cmF0X2NsdXN0ZXJzID09IDMgfiAiQiIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSA0IH4gIkZDR1IzQSsgTW9ub2N5dGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgcGJtYyRzZXVyYXRfY2x1c3RlcnMgPT0gNSB+ICJOSyIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSA2IH4gIk5haXZlIENENCsgVCIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSA3IH4gIlRoMSIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSA4IH4gIkludGVybWVkaWF0ZSBNb25vY3l0ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICBwYm1jJHNldXJhdF9jbHVzdGVycyA9PSA5IH4gIkRDIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBibWMkc2V1cmF0X2NsdXN0ZXJzID09IDEwIH4gIlBsYXRlbGV0IikKYGBgCgpXZSB2aXN1YWxpemUgdGhlIGZpbmFsIGNlbGxzIGxhYmVscyBmb3Igb3VyIDExIGNsdXN0ZXJzLiAgCgpgYGB7cn0KcDI2IDwtIERpbVBsb3QocGJtYywgY29scyA9IHBhbGV0dGVlcl9kKCJyY2FydG9jb2xvcjo6Vml2aWQiKSwgZ3JvdXAuYnkgPSAibGFiZWwiKSArIAogICAgICAgdGhlbWVfeWVobGFiKCkgKyAKICAgICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gMywgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKSArIAogICAgICAgbGFicyh4ID0gIkZpdC1TTkUgMSIsIHkgPSAiRml0LVNORSAyIiwgdGl0bGUgPSBOVUxMKQpwMjYKYGBgCgojIENvbmNsdXNpb25zCgpTQ0lTU09SUyBhdXRvbWF0aWNhbGx5IHNwbGl0IGEgbGFyZ2UgQ0Q0KyBUIGNsdXN0ZXIgaW50byBuYWl2ZSwgbWVtb3J5LCBhbmQgVGgxIENENCsgVCBjZWxscy4gSXQgc3VjY2Vzc2Z1bGx5IHNlcGFyYXRlZCBhIHNtYWxsIGRlbmRyaXRpYyBjZWxsIGNsdXN0ZXIgZnJvbSB0aGUgQ0QxNCsgbW9ub2N5dGVzIGl0IGhhZCBvcmlnaW5hbGx5IGJlZW4gZ3JvdXBlZCB3aXRoLCBhbmQgdGVhc2VkIG91dCB0aGUgaW50ZXJtZWRpYXRlIG1vbm9jeXRlIHBvcHVsYXRpb24gbmVzdGxlZCBiZXR3ZWVuIHRoZSBDRDE0KyBhbmQgQ0QxNisgbW9ub2N5dGUgY2x1c3RlcnMuIExhc3RseSwgaXQgaWRlbnRpZmllZCBhIHRpbnkgcGxhdGVsZXQgY2x1c3RlciB0aGF0IGhhZCBiZWVuIGVycm9uZW91c2x5IGdyb3VwZWQgd2l0aCB0aGUgQ0QxNisgbW9ub2N5dGVzLiBUaGUgaW50ZXJtZWRpYXRlIG1vbm9jeXRlIGFuZCBUaDEgY2VsbHMgd2VyZSBub3QgYW5ub3RhdGVkIGluIFt0aGUgb3JpZ2luYWwgU2F0aWphIExhYiBQQk1DM2sgdmlnbmV0dGVdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvdjMuMi9wYm1jM2tfdHV0b3JpYWwuaHRtbCkuIAoKV2UgdXNlZCB0aGUgUEJNQzNrIGRhdGFzZXQgYmVjYXVzZSBvZiAxKSBpdHMgaW1tZWRpYXRlIGF2YWlsYWJpbGl0eSB0byBhbnlvbmUgd2lzaGluZyB0byByZXBsaWNhdGUgb3VyIHJlc3VsdHMgYW5kIDIpIHRoZSB2YWxpZGl0eSBvZiBpdHMgYW5ub3RhdGlvbnMsIHdoaWNoIGFsbG93ZWQgdXMgdG8gYmUgY29uZmlkZW50IGluIHRoZSByZXN1bHRzIGZyb20gU0NJU1NPUlMsIHdoaWNoIHdhcyBhYmxlIHRvIGNhcnZlIG91dCByYXJlIGNlbGwgZ3JvdXBzIGZyb20gbGFyZ2VyLCBicm9hZGVyIGNlbGwgdHlwZXMuIEluIHRoaXMgY2FzZSwgdGhlIGRlbmRyaXRpYyBjZWxsIGNsdXN0ZXIgd2FzIGNvbXBvc2VkIG9mIDM5IGNlbGxzLCB0aGUgVGgxIGNsdXN0ZXIgb2YgNDAgY2VsbHMsIGFuZCB0aGUgcGxhdGVsZXQgY2x1c3RlciBvZiBqdXN0IDEzIGNlbGxzLiBIb3dldmVyLCBTQ0lTU09SUyBpc24ndCBqdXN0IHVzZWZ1bCBmb3IgcmFyZSBjZWxsIHR5cGVzOiBpdCBhbHNvIGlkZW50aWZpZWQgdGhlIGludGVybWVkaWF0ZSBtb25vY3l0ZXMsIGEgY2x1c3RlciBvZiAyMDYgY2VsbHMuIFdlIHRodXMgYmVsaWV2ZSB3ZSBjYW4gY29uZmlkZW50bHkgc2F5IHRoYXQgU0NJU1NPUlMgaGFzIGJlZW4gc2hvd24gdG8gYWNjdXJhdGVseSBhbmQgc3dpZnRseSBpZGVudGlmeSBjZWxsIHR5cGVzLCBib3RoIGNvbW1vbiBhbmQgcmFyZSwgYnkgY29uc2lkZXJpbmcgdGhlIHZhcmlhbmNlIGluIGdlbmUgZXhwcmVzc2lvbiB3aXRoaW4gY2x1c3RlcnMgYW5kIGp1ZGdpbmcgaXRlcmF0aXZlIHJlY2x1c3RlcmluZyB1c2luZyBzaWxob3VldHRlIHNjb3Jlcy4gV2UgcHV0IGZvcnRoIHRoYXQgdGhpcyBhcHByb2FjaCBpcyB0aGVvcmV0aWNhbGx5IGFkdmFudGFnZW91cyBmb3IgaWRlbnRpZnlpbmcgcmFyZSBjZWxsIHBvcHVsYXRpb25zLCByYXRoZXIgdGhhbiBhdHRlbXB0aW5nIHRvIGRvIHNvIGF0IHRoZSBsZXZlbCBvZiB0aGUgZW50aXJlIGRhdGFzZXQuCgojIFNhdmUgRGF0YSAmIEZpZ3VyZXMKClRoaXMgcGFydCBpc24ndCByZWFsbHkgd29ydGggcmVhZGluZzsgaXQncyBqdXN0IGhlcmUgdG8gcHJvdmUgdGhhdCBhbGwgdGhlIGZpZ3VyZXMgd2VyZSBhY3R1YWxseSBkeW5hbWljYWxseSBnZW5lcmF0ZWQgYW5kIHNhdmVkIHVwb24ga25pdHRpbmcgdGhpcyBkb2N1bWVudC4KCldlJ2xsIGNyZWF0ZSBhIHF1aWNrIGNvbnZlbmllbmNlIGZ1bmN0aW9uIHRvIGhlbHAgdXMgc2F2ZSB0aGUgZmlndXJlcy4KCmBgYHtyfQpzYXZlU0NJU1NPUlMgPC0gZnVuY3Rpb24ocGxvdCA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwdWIucmVhZHkgPSBGQUxTRSkgewogIGlmIChpcy5udWxsKHBsb3QpIHwgaXMubnVsbChuYW1lKSkgc3RvcCgiWW91IGZvcmdvdCBzb21lIGFyZ3VtZW50cy4iKQogIGlmIChwdWIucmVhZHkpIHsKICAgIGRpciA8LSAifi9EZXNrdG9wL1IvU0NJU1NPUlMvdmlnbmV0dGVzL2ZpZ3VyZXNfcHViL1BCTUMiCiAgICBpZiAoIWJvcmRlcikgewogICAgICBwbG90IDwtIHBsb3QgKyAKICAgICAgICAgICAgICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICAgIH0gZWxzZSB7CiAgICAgIHBsb3QgPC0gcGxvdCArIAogICAgICAgICAgICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICAgIH0KICAgIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMChuYW1lLCAiLnBkZiIpLCAKICAgICAgICAgICBkZXZpY2UgPSAicGRmIiwgCiAgICAgICAgICAgdW5pdHMgPSAiaW4iLAogICAgICAgICAgIHBhdGggPSBkaXIsIAogICAgICAgICAgIGhlaWdodCA9IDgsIAogICAgICAgICAgIHdpZHRoID0gOCkgCiAgfSBlbHNlIHsKICAgIGRpciA8LSAifi9EZXNrdG9wL1IvU0NJU1NPUlMvdmlnbmV0dGVzL2ZpZ3VyZXNfc3VwcC9QQk1DIgogICAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKG5hbWUsICIucGRmIiksIAogICAgICAgICAgIGRldmljZSA9ICJwZGYiLCAKICAgICAgICAgICB1bml0cyA9ICJpbiIsCiAgICAgICAgICAgcGF0aCA9IGRpciwgCiAgICAgICAgICAgaGVpZ2h0ID0gOCwgCiAgICAgICAgICAgd2lkdGggPSA4KSAKICB9Cn0KYGBgCgpMYXN0bHksIHdlJ2xsIHNhdmUgdGhlIGZpZ3VyZXMgdW5kZXIgYC4vdmlnbmV0dGVzL2ZpZ3VyZXMvYC4gCgpgYGB7cn0Kc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMCwgbmFtZSA9ICJTZXVyYXRfQ2x1c3RlcnMiLCBwdWIucmVhZHkgPSBUUlVFLCBib3JkZXIgPSBGQUxTRSkKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMSwgbmFtZSA9ICJTZXVyYXRfQ2x1c3RlcnNfRml0U05FIiwgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDIsIG5hbWUgPSAiQ2x1c3QwX1JlY2x1c3QiLCBwdWIucmVhZHkgPSBUUlVFLCBib3JkZXIgPSBGQUxTRSkKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMywgbmFtZSA9ICJDbHVzdDFfUmVjbHVzdCIsIHB1Yi5yZWFkeSA9IFRSVUUsIGJvcmRlciA9IEZBTFNFKQpzYXZlU0NJU1NPUlMocGxvdCA9IHA0LCBuYW1lID0gIkNsdXN0Ml9SZWNsdXN0IiwgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDUsIG5hbWUgPSAiU0NJU1NPUlNfQ2x1c3RlcnMiLCBwdWIucmVhZHkgPSBUUlVFLCBib3JkZXIgPSBGQUxTRSkKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwNiwgbmFtZSA9ICJDRDRUX0lMN1IiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHA3LCBuYW1lID0gIkNENFRfQ0NSNyIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDgsIG5hbWUgPSAiQ0Q0VF9TMTAwQTQiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHA5LCBuYW1lID0gIlRIMV9JRklUMSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDEwLCBuYW1lID0gIlRIMV9JRklUMyIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDExLCBuYW1lID0gIlRIMV9JRkk2IikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMTIsIG5hbWUgPSAiTW9ub2N5dGVfQ0QxNCIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDEzLCBuYW1lID0gIk1vbm9jeXRlX0xZWiIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDE0LCBuYW1lID0gIkZDR1IzQV9Nb25vY3l0ZV9GQ0dSM0EiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAxNSwgbmFtZSA9ICJGQ0dSM0FfTW9ub2N5dGVfTVM0QTciKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAxNiwgbmFtZSA9ICJJbnRlcm1lZGlhdGVfTW9ub19ITEFEUEIxIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMTcsIG5hbWUgPSAiSW50ZXJtZWRpYXRlX01vbm9fQ0Q3NCIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDE4LCBuYW1lID0gIkludGVybWVkaWF0ZV9Nb25vX0hMQURSQSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDE5LCBuYW1lID0gIkJfTVM0QTEiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyMCwgbmFtZSA9ICJDRDhUX0NEOEEiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyMSwgbmFtZSA9ICJOS19OS0c3IikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMjIsIG5hbWUgPSAiTktfR05MWSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDIzLCBuYW1lID0gIkRDX0ZDRVIxQSIpCnNhdmVTQ0lTU09SUyhwbG90ID0gcDI0LCBuYW1lID0gIkRDX0NTVDMiKQpzYXZlU0NJU1NPUlMocGxvdCA9IHAyNSwgbmFtZSA9ICJQbGF0ZWxldF9QUEJQIikKc2F2ZVNDSVNTT1JTKHBsb3QgPSBwMjYsIG5hbWUgPSAiU0NJU1NPUlNfZmluYWxfbGFiZWxzIiwgcHViLnJlYWR5ID0gVFJVRSwgYm9yZGVyID0gRkFMU0UpCmBgYAoKQW5kIG9mIGNvdXJzZToKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=